package com.neuronbit.businessflow.flow.source;

import com.neuronbit.businessflow.flow.parser.Output;
import com.neuronbit.businessflow.flow.parser.StatefulTaskNode;
import com.neuronbit.businessflow.flow.source.util.FieldBuilder;
import com.neuronbit.businessflow.flow.source.util.SourceContext;
import com.neuronbit.businessflow.intellij.model.*;
import com.neuronbit.businessflow.runtime.StepType;
import com.neuronbit.javaparser.ast.Modifier;
import com.neuronbit.javaparser.ast.body.MethodDeclaration;
import com.neuronbit.javaparser.ast.expr.MethodCallExpr;
import com.neuronbit.javaparser.ast.expr.NameExpr;
import com.neuronbit.javaparser.ast.expr.UnaryExpr;
import com.neuronbit.javaparser.ast.stmt.BlockStmt;
import com.neuronbit.javaparser.ast.stmt.IfStmt;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static java.lang.String.format;

/**
 * The type Stateful task node source generator.
 */
@Getter
@Setter
public class StatefulTaskStepJavaSource extends UserTaskStepJavaSource {
    /**
     * Instantiates a new Auto task node source generator.
     *
     * @param node          the node
     * @param sourceContext the source context
     */
    public StatefulTaskStepJavaSource(Node node, SourceContext sourceContext) {
        super(node, sourceContext);
    }

    @Override
    protected StepType getStepType() {
        return StepType.AUTO_TASK;
    }

    @Override
    protected void writeStepCallStmt(BlockStmt stepCallStmt) {
        StatefulTaskNode autoTaskNode = (StatefulTaskNode) getNode();
        Output output = autoTaskNode.getOutput();
        String re = "";
        String setStmt = null;
        if (output != null) {
            addImport(output.getType());
            re = output.getType().describe() + " " + output.getName() + "=";
            FieldBuilder fieldBuilder = FieldBuilder.builder(output.getName(), output.getType())
                    .withSetter()
                    .withGetter()
                    .prefix(getNode().getName());
            getContextSource().addField(fieldBuilder);
            setStmt = format("context.set%s(%s);", getNode().getCapitalizeName() + output.getCapitalizeName(), output.getName());
        }
        //call the actual business processing logic
        generateStateChecker(stepCallStmt, autoTaskNode, re);

        Type type = autoTaskNode.getType();
        String method = autoTaskNode.getMethodName();
        if (StringUtils.isNoneBlank(re, method) && null != type) {
            addImport(type);
            stepCallStmt.addStatement(format("%s flow.getStepBeanProvider().getBean(%s.class).%s(%s);",
                    re, type.getName(), method, String.join(",", prepareStepParams(stepCallStmt))));
        }
        if (null != setStmt) {
            stepCallStmt.addStatement(setStmt);
        }
    }

    private void generateStateChecker(BlockStmt stepCallStmt, StatefulTaskNode autoTaskNode, String re) {
        Type type = autoTaskNode.getStateChecker().getType();
        FormattedMethodString method = autoTaskNode.getStateChecker().getMethod();
        if (method != null && !method.isBlank() && StringUtils.isNoneBlank(re) && null != type) {
            addImport(type);
            addImport(SimpleType.of("com.neuronbit.businessflow.runtime.exception.InvalidFlowStepStateException"));
            MethodDeclaration checkStepStateMethod = getStepClass().addMethod("checkStepState", Modifier.Keyword.PUBLIC);
            checkStepStateMethod.setType(boolean.class);
            checkStepStateMethod.addParameter(getContextSource().getSimpleName(), "context");
            checkStepStateMethod.addParameter("Flow", "flow");
            checkStepStateMethod.addThrownException(Exception.class);
            BlockStmt checkStepStateMethodBody = new BlockStmt();
            checkStepStateMethod.setBody(checkStepStateMethodBody);
            checkStepStateMethodBody.addStatement(format("return flow.getStepBeanProvider().getBean(%s.class).%s(%s);",
                    type.getName(), method.getName(), String.join(",", prepareStateCheckerParams(checkStepStateMethodBody))));

            IfStmt ifStmt = new IfStmt();
            ifStmt.setCondition(new UnaryExpr(new MethodCallExpr("checkStepState", new NameExpr("context"), new NameExpr("flow")), UnaryExpr.Operator.LOGICAL_COMPLEMENT));
            BlockStmt thenStmt = new BlockStmt();
            thenStmt.addStatement("throw new InvalidFlowStepStateException();");
            ifStmt.setThenStmt(thenStmt);
            stepCallStmt.addStatement(ifStmt);
        }
    }

    protected List<String> prepareStateCheckerParams(BlockStmt methodBody) {
        Map<String, Param> inputs = ((StatefulTask) getNode()).getStateChecker().getParams();
        List<String> paramNames = new ArrayList<>();
        if (null != inputs) {
            for (Param input : inputs.values()) {
                if (null == input) {
                    continue;
                }
                //TODO shunyun 2024/1/17: defaultValue
                ParamValue value = input.getValue();
                if (null == value) {
                    continue;
                }
                String ex = processValueSource(value, getFlowNodeSource());
                if (StringUtils.isNotBlank(ex)) {
                    addImport(input.getType());
                    methodBody.addStatement(format("%s %s = %s;",
                            input.getType().getName(),
                            input.getName(),
                            ex
                    ));
                    paramNames.add(input.getName());
                }
            }
        }

        return paramNames;
    }
}
